package cn.jiedanba.itext.gm;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.UUID;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.EncryptionAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.TSAClient;
import com.itextpdf.text.pdf.security.TSAClientBouncyCastle;

import cn.jiedanba.itext.util.IOUtil;

/**
 * itext5 国密电子签名
 * 
 * @author dell
 *
 */
public class ITextSm2Util {

	private static final Logger log = LoggerFactory.getLogger(ITextSm2Util.class);

	private static final Provider BC = new BouncyCastleProvider();

	static {
		Security.addProvider(BC);
	}

	/**
	 * SM2电子签名
	 * 
	 * @param pdf
	 *            pdf文件
	 * @param stampImage
	 *            签名图片
	 * @param chain
	 *            证书链
	 * @param pk
	 *            SM2私钥
	 * @param reason
	 *            签名理由
	 * @param location
	 *            签名位置
	 * @param tsa
	 *            时间戳Url
	 * @param positionX
	 *            签名X坐标
	 * @param positionY
	 *            签名Y坐标
	 * @param width
	 *            签名高度
	 * 
	 * @param height
	 *            签名宽度
	 * @param pageNo
	 *            签名页数
	 * @return
	 */
	public static byte[] sign(byte[] pdf, byte[] stampImage, Certificate[] chain, PrivateKey pk, String reason,
			String location, String tsa, float positionX, float positionY, float width, float height, int pageNo) {

		ByteArrayOutputStream tmpos = new ByteArrayOutputStream();
		try {
			PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf));

			// 创建签章工具PdfStamper ，最后一个boolean参数
			// false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
			// true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
			PdfStamper stamper = PdfStamper.createSignature(reader, tmpos, '\0', null, true);
			// 获取数字签章属性对象，设定数字签章的属性
			PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
			appearance.setReason(reason);
			appearance.setLocation(location);
			// 设置签名的位置，页码，签名域名称，多次追加签名的时候，签名预名称不能一样
			// 签名的位置，是图章相对于pdf页面的位置坐标，原点为pdf页面左下角
			// 四个参数的分别是，图章左下角x，图章左下角y，图章右上角x，图章右上角y
			appearance.setVisibleSignature(
					new Rectangle(positionX, positionY, (positionX + width), (positionY + height)), pageNo,
					UUID.randomUUID().toString());
			// 读取图章图片，这个image是itext包的image
			Image image = Image.getInstance(stampImage);
			appearance.setSignatureGraphic(image);
			appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
			// 设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
			appearance.setRenderingMode(RenderingMode.GRAPHIC);

			// 摘要算法
			ExternalDigest digest = new ExternalDigest() {
				@Override
				public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException {
					return MessageDigest.getInstance("SM3", BC);
				}
			};

			Field digestNamesField = DigestAlgorithms.class.getDeclaredField("digestNames");
			digestNamesField.setAccessible(true);
			HashMap<String, String> digestNames = (HashMap<String, String>) digestNamesField.get(null);
			digestNames.put("1.2.156.10197.1.401", "SM3");

			Field allowedDigests = DigestAlgorithms.class.getDeclaredField("allowedDigests");
			allowedDigests.setAccessible(true);
			HashMap<String, String> allowedDigestsNames = (HashMap<String, String>) allowedDigests.get(null);
			allowedDigestsNames.put("SM3", "1.2.156.10197.1.401");

			Field algorithmNamesField = EncryptionAlgorithms.class.getDeclaredField("algorithmNames");
			algorithmNamesField.setAccessible(true);
			HashMap<String, String> algorithmNames = (HashMap<String, String>) algorithmNamesField.get(null);
			algorithmNames.put("1.2.156.10197.1.501", "SM2");

			// 签名算法
			ExternalSignature signature = new Sm2PrivateKeySignature(pk, "BC");

			TSAClient tsaClient = new TSAClientBouncyCastle(tsa);
			// 调用itext签名方法完成pdf签章
			SM2MakeSignature.signDetached(appearance, digest, signature, chain, null, null, tsaClient, 0,
					CryptoStandard.CMS);
			return tmpos.toByteArray();
		} catch (Exception e) {
			log.error("PDF签名异常！", e);
			throw new RuntimeException(e);
		}

	}

	public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException,
			CertificateException, InvalidKeySpecException {

		// 国密时间戳
		String sm2Tsa = "http://47.98.124.142:9091/service/tsa?type=sm2";

		// pdf
		byte[] pdf = IOUtil.fileToByte("C:\\Users\\dell\\Desktop\\测试SM2电子签章.pdf");

		// 签章图片
		byte[] stampImage = IOUtil.fileToByte("E:\\资料\\资料\\7b2d9afeb5afcee3794b30782ef7b74.png");

		// 私钥
		byte[] priv = Base64.decodeBase64(
				"MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgSRirZ921GY5b4CniFC/Vzs0qxEUbdWAE2mLdT8Otvo2gCgYIKoEcz1UBgi2hRANCAASxqgeEslQrzH4S74/NCU718jrUcaDaDu0fKkycLmND/MEcx96/bUB2nM7vDvUkf9l05JiAjjAV9r632zzhA3Cl");

		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priv);
		KeyFactory keyFactory;
		keyFactory = KeyFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
		PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

		// 签名证书
		byte[] cert = Base64.decodeBase64(
				"MIIDLzCCAtSgAwIBAgIGAXYmhEVJMAoGCCqBHM9VAYN1MGcxCzAJBgNVBAYTAkNOMT8wPQYDVQQKDDZTaGVuemhlbiBEaWdpdGFsIENlcnRpZmljYXRlIEF1dGhvcml0eSBDZW50ZXIgQ28uLCBMdGQxFzAVBgNVBAMMDlNNMiBQdWJsaWMgQ0ExMB4XDTIwMTIwMzAyNTI1MVoXDTIxMTIwMzAyNTI1MVowfTELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCeW5v+S4nOecgTESMBAGA1UEBwwJ5rex5Zyz5biCMRswGQYDVQQKDBLnlLXlrZDnrb7nq6DkuJPnlKgxEjAQBgNVBAsMCVNNMueul+azlTEVMBMGA1UEAwwM5rWL6K+V6K+B5LmmMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEsaoHhLJUK8x+Eu+PzQlO9fI61HGg2g7tHypMnC5jQ/zBHMfev21AdpzO7w71JH/ZdOSYgI4wFfa+t9s84QNwpaOCAVQwggFQMA4GA1UdDwEB/wQEAwIE8DAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQbW4+HGQHVeAp4t6HydVFkie6QdjAfBgNVHSMEGDAWgBSlngBfiohI8QwGfHbJI6n277js8zA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vNDcuOTguMTI0LjE0Mjo4MDg1L1NNMlB1YmxpY0NBMS5jcmwwawYIKwYBBQUHAQEEXzBdMCMGCCsGAQUFBzABhhdodHRwOi8vb3NjcC5qaWVkYW5iYS5jbjA2BggrBgEFBQcwAoYqaHR0cDovLzQ3Ljk4LjEyNC4xNDI6ODA4NS9TTTJQdWJsaWNDQTEuY3JsMEYGA1UdIAEB/wQ8MDowOAYJYIZIAYb9bAEBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly80Ny45OC4xMjQuMTQyOjgwODUvY3BzMAoGCCqBHM9VAYN1A0kAMEYCIQC4lTDnYtdbXVNI/oE7yrks32Id1Ttwf07z4QkWwM1TxAIhAOWlQUvgdDJ11+MtfqhwNPpD/edONb7r22Hxo3EjSHlM");

		CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509",
				BouncyCastleProvider.PROVIDER_NAME);
		Collection<Certificate> chainList = new ArrayList<Certificate>(
				certificatefactory.generateCertificates(new ByteArrayInputStream(cert)));

		byte[] signPdf = sign(pdf, stampImage, chainList.toArray(new Certificate[] {}), privateKey, "数字签名，不可否认",
				"广东省深圳市南山区", sm2Tsa, 400, 200, 80, 80, 1);

		FileUtils.writeByteArrayToFile(new File("C:\\Users\\dell\\Desktop\\sign.pdf"), signPdf);

	}

}
